#!/bin/bash

# Figure out which versions of glibc are available
export GLIBC_GOOD_VERSIONS=""
export GLIBC_BAD_VERSIONS=""
export GLIBC_IFDEFS=""
export LIBC_LOCATION=`ldd $1 | grep "libc.so.6" | awk ' { print $3 } '`
echo "libc located as: "$LIBC_LOCATION
# Add ones we have on the local system to the good list, and ones we don't on the bad list
# Also build a set of defined variables for use with the C file.
for glibcs in GLIBC_2.6 GLIBC_2.7 GLIBC_2.8 GLIBC_2.9 GLIBC_2.10 GLIBC_2.11 GLIBC_2.12 GLIBC_2.13 GLIBC_2.14 GLIBC_2.15
do
   if [ -n "`readelf -V $LIBC_LOCATION | grep -F $glibcs`" ]; then
      export GLIBC_GOOD_VERSIONS="$GLIBC_GOOD_VERSIONS $glibcs"
   else
      export GLIBC_BAD_VERSIONS="$GLIBC_BAD_VERSIONS $glibcs"
      export GLIBC_IFDEFS="$GLIBC_IFDEFS `echo $glibcs | awk ' BEGIN { FS="."; } { print "-DNO_"$1"_"$2 } '`"
   fi
done

rm glibc_patch.so
rm patch.log
# Get a list of files to patch
export FILES_TO_PATCH="$@ `echo *.so*`"
echo "Applying weak versioning patches to "$FILES_TO_PATCH
chmod +w $FILES_TO_PATCH
# Preprocess the version map to include only the necessary function patches.
gcc $GLIBC_IFDEFS -E -c glibc_patch_version.map.c -o glibc_patch_specific.map
# Compile the glibc patch shared library.
gcc -g $GLIBC_IFDEFS -fPIC -shared -o glibc_patch.o -c glibc_patch.c
gcc -g $GLIBC_IFDEFS -fPIC -shared -Wl,--version-script=glibc_patch_specific.map -Wl,-soname,glibc_patch.so -o glibc_patch.so glibc_patch.o

for patchingfiles in $FILES_TO_PATCH
do
   # Enforce an OS/ABI flag of SYSV - other Linux distros may set this to \x03 - Linux
   echo "patching $patchingfiles to SYSV"
   echo "patching $patchingfiles to SYSV" >> patch.log
   printf '\x00' | dd of=$patchingfiles seek=7 bs=1 count=1 conv=notrunc &> patch.log
   for forbiddenglibc in $GLIBC_BAD_VERSIONS
   do
      # Find the entry in the version table for the bad glibc versions
      export SECONDARY_OFFSET_LINE=`readelf -V $patchingfiles | grep -F "Name: "$forbiddenglibc`
      if [ -n "$SECONDARY_OFFSET_LINE" ]; then
         export SECONDARY_OFFSET=`echo $SECONDARY_OFFSET_LINE | sed 's:^0x::g;s/://g;s: 0x: :g;s: 0*: :g;s:\(.*\):\U\1:' | awk ' { print "ibase=16; "$1 } ' | bc`
         export PRIMARY_OFFSET_LINE=`readelf -V $patchingfiles | grep -A +1 -F "section '.gnu.version_r'" | grep Offset`
         if [ -n "$PRIMARY_OFFSET_LINE" ]; then
            export PRIMARY_OFFSET=`echo $PRIMARY_OFFSET_LINE | sed 's:^0x::g;s/://g;s: 0x: :g;s: 0*: :g;s:\(.*\):\U\1:' | awk ' { print "ibase=16; "$4 } ' | bc`
            # Patch the glibc reference to make it a weak reference - will still error but will not crash out
            echo "patching located reference to BAD $forbiddenglibc in $patchingfiles at offset $PRIMARY_OFFSET + $SECONDARY_OFFSET"
            echo "patching located reference to BAD $forbiddenglibc in $patchingfiles at offset $PRIMARY_OFFSET + $SECONDARY_OFFSET" >> patch.log
            printf '\x06' | dd of=$patchingfiles seek=$((PRIMARY_OFFSET + SECONDARY_OFFSET + 4)) bs=1 count=1 conv=notrunc &> patch.log
         fi
      fi
      # If we find no support for GLIBC_2.12/11, we need to use locally available pthread libraries.
      if [ "$patchingfiles" == *pthread.so* ] && [ "$forbiddenglibc" == "GLIBC_2.12" ]; then
         echo "Found BAD glibc $forbiddenglibc in the library $patchingfiles" >> patch.log
         echo "No real fix - use the native pthread library!" >> patch.log
         echo "Found BAD glibc $forbiddenglibc in the library $patchingfiles"
         echo "No real fix - use the native pthread library!"
         rm *pthread.so*
      fi
      if [ "$patchingfiles" == *pthread.so* ] && [ "$forbiddenglibc" == "GLIBC_2.11" ]; then
         echo "Found BAD glibc $forbiddenglibc in the library $patchingfiles" >> patch.log
         echo "No real fix - use the native pthread library!" >> patch.log
         echo "Found BAD glibc $forbiddenglibc in the library $patchingfiles"
         echo "No real fix - use the native pthread library!"
         rm *pthread.so*
      fi
   done
done

echo "patching symbols marked as OS specific"
for patchingfiles in $FILES_TO_PATCH
do
   export PRIMARY_OFFSET_LINE=`readelf -S $patchingfiles | grep "\.dynsym"`
   export DYNSYM_COUNT=`readelf --syms $patchingfiles | grep dynsym | awk ' { print $5 } '`
   export PRIMARY_OFFSET=`echo $PRIMARY_OFFSET_LINE | sed 's/\[//g;s/\]//g;s:\(.*\):\U\1:' | awk ' { print "ibase=16; "$5 } ' | bc`
   export SECONDARY_LIST=`readelf --syms $patchingfiles | head -n $((DYNSYM_COUNT + 3)) | grep "<OS specific>" | sed 's/:/ /g' | awk ' { print $1 } ' | xargs echo`
   for SECONDARY_OFFSET in $SECONDARY_LIST
   do
      echo "patching dynamic symbol class at offset $PRIMARY_OFFSET + $((SECONDARY_OFFSET*24)) + 4"
      printf '\x02' | dd of=$patchingfiles seek=$((PRIMARY_OFFSET + (SECONDARY_OFFSET*24) + 4)) bs=1 count=1 conv=notrunc &> patch.log
   done
   export PRIMARY_OFFSET_LINE=`readelf -S $patchingfiles | grep "\.symtab"`
   export PRIMARY_OFFSET=`echo $PRIMARY_OFFSET_LINE | sed 's/\[//g;s/\]//g;s:\(.*\):\U\1:' | awk ' { print "ibase=16; "$5 } ' | bc`
   export SECONDARY_LIST=`readelf --syms $patchingfiles | grep "<OS specific>" | sed 's/:/ /g' | awk ' { print $1 } ' | xargs echo`
   for SECONDARY_OFFSET in $SECONDARY_LIST
   do
      echo "patching normal symbol class at offset $PRIMARY_OFFSET + $((SECONDARY_OFFSET*24)) + 4"
      printf '\x02' | dd of=$patchingfiles seek=$((PRIMARY_OFFSET + (SECONDARY_OFFSET*24) + 4)) bs=1 count=1 conv=notrunc &> patch.log
   done
done
